//-----------------------------------------
//------- User code example ---------------
//-----------------------------------------

#include <core.p4>
#include <v1_model.p4>

#include "metadata.p4"
#include "parser.p4"

#define VHOST_TABLE_SIZE 256
#define PORTNUM 32

// ---------------HEADERS---------------
const bit<8>  IPV6_HBH_OPTION = 0x00;
const bit<8>  ICMP_PROTOCOL = 0x01;
const bit<8>  IGMP_PROTOCOL = 0x02;
const bit<8>  TCP_PROTOCOL = 0x06;
const bit<8>  UDP_PROTOCOL = 0x11;
const bit<8>  GRE_PROTOCOL = 0x2F;
const bit<8>  ESP_PROTOCOL = 0x32;
const bit<8>  AH_PROTOCOL = 0x33;
const bit<8>  ICMP6_PROTOCOL = 0x3A;


/* Ethertype */
const bit<16> TYPE_VLAN = 0x8100;
const bit<16> TYPE_IPV4 = 16w0x0800;
const bit<16> TYPE_IPV6 = 16w0x86DD;
const bit<16> TYPE_ARP = 0x0806;
const bit<16> TYPE_CONTROL = 0x0808;
const bit<16> TYPE_MPLS = 0x8848;
const bit<16> TYPE_PTP = 0x88F7;
const bit<16> TYPE_FCOE = 0x8906;
const bit<16> TYPE_ROCE = 0x8915;  // v1

/* UDP ports */
// const bit<16> ROCE_PORT = 4791;   // v2
const bit<16> VXLAN_PORT = 4789;
// const bit<16> GENEVE_PORT = 6081;
// const bit<16> PTP_EVENT_PORT = 319;
// const bit<16> PTP_GEN_PORT = 320;
// const bit<5>  IPV4_OPTION_MRI = 31;

#define MAX_HOPS 9

@ethernetaddress typedef bit<48> EthernetAddress;
@ipv4address     typedef bit<32>     IPv4Address;
@ipv6address     typedef bit<128>     IPv6Address;

/******/
/* L2 */ 
/******/
// standard Ethernet header
header Ethernet_h
{
    EthernetAddress dst_addr;
    EthernetAddress src_addr;
    bit<16> ether_type;
}

header Vlan_h 
{
    bit<3>  pcp;   /* also called pri */
	bit     dei;   /* also called cfi */
	bit<12> vlan_id;   /* vlan ID */
	bit<16> ether_type;
}

/******/
/* L3 */ 
/******/
// IPv4 header without options
header IPv4_h {
    bit<4>       version;
    bit<4>       ihl;
    bit<6>       diffserv;
    bit<2>       ecn;
    bit<16>      total_len;
    bit<16>      identification;
    bit<3>       flags;
    bit<13>      frag_offset;
    bit<8>       ttl;
    bit<8>       protocol;
    bit<16>      hdr_checksum;
    IPv4Address  src_addr;
    IPv4Address  dst_addr;
}

header IPv6_h {
    bit<4>       version;
    bit<8>       traffic_class;
    bit<20>      flow_label;
    bit<16>      payload_len;
    bit<8>       next_hdr;   // same as ipv4 protocol
    bit<8>       hop_limit;
    IPv6Address       src_addr;
    IPv6Address       dst_addr;
}

// Only one option is allowed
header_union IP_h {
  IPv4_h v4;
  IPv6_h v6;
}

header Mpls_h {
    bit<20> label;
    bit<3> tc;
    bit<1> bos;
    bit<8> ttl;
}
header Grh_h {
    // TODO
}
header Fcoe_h {
    // TODO
}

header Arp_h {
    bit<16> hw_type;
    bit<16> proto_type;
    bit<8> hw_addr_len;
    bit<8> proto_addr_len;
    bit<16> opcode;
    // TODO
}
header Ptp_h {
    bit<4> msg_type;
    bit<16> seq_id;
    bit<8> domain_num;
    bit<4> ver;
    // TODO
}
header Control_h {
    bit<16> opcode;
    bit<16> pause_time;
    bit<16> class_enable;
    bit<128> pfc_pause_time;
    // TODO
}
header Raw_h {
    bit<192> eth_payload;
    bit<6> payload_valid;
    // TODO
}

/******/
/* L4 */ 
/******/
header Icmp_h {
    bit<8> icmp_type;
    bit<8> icmp_code;
    bit<16> checksum;
    bit<16> identifier;
    bit<16> sequence_number;
    bit<64> timestamp;
}

header Tcp_h {
    bit<16> src_port;
    bit<16> dst_port;
    bit<32> seq_no;
    bit<32> ack_no;
    bit<4>  data_offset;
    bit<4>  res;
    bit<8>  flags;
    bit<16> window;
    bit<16> checksum;
    bit<16> urgent_ptr;
}

header Udp_h {
    bit<16> src_port;
    bit<16> dst_port;
    bit<16> length;
    bit<16> checksum;
}

header Ah_h {
    bit<8> nextProtocol;
    bit<32> spi;
}

header Esp_h  {
    bit<32> spi;
}

header Icmp_v4_h {
    bit<8> type;
    bit<8> code;
    //bit<16> checksum;
}

header Icmp_v6_h {
    bit<8> type;
    bit<8> code;
    //bit<16> checksum;
}

header Bth_h {
    bit<8>      opcode;
    bit         solicited_event;
    bit         mitigation_state;
    bit<2>      pad_cnt;
    bit<4>      transport_hdr_ver;
    bit<16>     parition_key;
    bit         fecn;
    bit         becn;
    bit<6>      reserved1;
    bit<24>     dest_queue_pair;
    bit         ack_req;
    bit<7>      reserved2;
    bit<24>     pkt_seq_num;
    bit<16>     zero_padding;
    bit<4>      icrc;
}

header Gre_h     {
    bit    C;
    bit    R;
    bit    K;
    bit    S;
    bit    s;
    bit<3> recur;
    bit<5> flags;
    bit<3> ver;
    // rest are options based on flags
}

/******/
/* Application or L7 */
/******/

header Vxlan_h {
    bit<4>   reserved1;
    bit      isValid;
    bit<3>   reserved2;
    bit<24>  reserved3;
    bit<24>  vni;
    bit<8>   reserved4;
}

header Vxlan_gpe_h{
    bit<4>   reserved1;
    bit      isValid;
    bit<3>   reserved2;
    bit<16>  reserved3;
    bit<8>   next_proto;
    bit<24>  vni;
    bit<8>   reserved4;
}

header Rtp_h {
    bit<2> ver;
    bit<1> pad;
    bit<1> ext;
    bit<4> csrc_cnt;
    bit<1> marker;
    bit<7> payload_type;
    bit<16> seq;
    bit<32> timestamp;
    bit<32> ssrc_id;
}

//-------------METADATA---------------
typedef bit<64> sai_object_id_t;
typedef bit<32> sai_ip4_t;

@name("standard_metadata")
struct standard_metadata_t {
}

//@name("in_port_metadata")
//struct in_port_metadata_t {
//		sai_object_id_t ingress_port;
//}

//@name("out_port_metadata")
//struct out_port_metadata_t {
//		sai_object_id_t egress_port;
//}

//@name("in_rif_metadata")
//struct in_rif_metadata_t {
//		sai_object_id_t ingress_rif;
//}

//@name("out_rif_metadata")
//struct out_rif_metadata_t {
//		sai_object_id_t egress_rif;
//}

@name("user_metadata")
struct user_metadata_t {
    bit<12> vnet_bitmap;
    sai_object_id_t ingress_rif;
		sai_object_id_t egress_rif;
        sai_object_id_t ingress_port;
        sai_object_id_t egress_port;
}

//-------------PARSER---------------
struct Headers_t
{
    Ethernet_h ethernet;
    Vlan_h     vlan;
    IP_h       ip;
    Mpls_h     mpls;
 // Grh_h      grh;
    Fcoe_h     fcoe;
    Arp_h      arp;
    Ptp_h      ptp;
    Control_h  ctl;
    Raw_h      raw;

    Ah_h       ah;
    Esp_h      esp;
    Icmp_v4_h  icmp4;
    Icmp_v6_h  icmp6;
    Bth_h      bth;
    Gre_h      gre;
    Tcp_h      tcp;
    Udp_h      udp;

    Ptp_h      ptp_udp;
    Vxlan_h    vxlan;
    Vxlan_gpe_h vxlangpe;
    // Deth_h     deth;
    // Nd_h       nd;
}

// Parser section
// This describes the default parser graph state machine 
parser Parser(packet_in p, out Headers_t headers, inout user_metadata_t user_meta, inout standard_metadata_t standard_metadata) 
{
    state start
    {
        p.extract(headers.ethernet);
        transition select(headers.ethernet.ether_type)
        {
            TYPE_VLAN : parse_vlan;
            TYPE_IPV4 : parse_ipv4;    // 16w0x800
            TYPE_IPV6 : parse_ipv6;    // 16w0x86DD
            TYPE_ARP  : parse_arp;
            TYPE_PTP  : parse_ptp;
            TYPE_MPLS : parse_mpls;
 //         TYPE_GRH  : parse_grh;
            TYPE_FCOE : parse_fcoe;
            TYPE_CONTROL : parse_control;

            // other parser states go here
            // default : reject;
            default : parse_raw;
        }
    }

    state parse_vlan {
        p.extract(headers.vlan);
        transition select(headers.vlan.ether_type) {
            TYPE_IPV4 : parse_ipv4;
            TYPE_IPV6 : parse_ipv6;    // 16w0x86DD
            TYPE_ARP  : parse_arp;
            TYPE_PTP  : parse_ptp;
            TYPE_MPLS : parse_mpls;
 //         TYPE_GRH  : parse_grh;
            TYPE_FCOE : parse_fcoe;
            TYPE_CONTROL : parse_control;
            default: accept;
        }
    }

    state parse_ipv4
    {
        p.extract(headers.ip.v4);
        // verify(headers.ip.v4.version == 4w4, error.IPv4IncorrectVersion);
        // verify(headers.ip.v4.ihl == 4w5, error.IPv4OptionsNotSupported);
        transition select(headers.ip.v4.protocol)
        {
            TCP_PROTOCOL : parse_tcp;
            UDP_PROTOCOL : parse_udp;
            // custom parser states go here

            // no default rule: all other packets rejected
            default : accept;
        }
    }

    state parse_ipv6
    {
        p.extract(headers.ip.v6);
        transition accept;
    }

    state parse_arp
    {
        //p.extract(headers.arp);
        transition accept;
    }

    state parse_tcp
    {
        p.extract(headers.tcp);
        transition accept;
    }

    state parse_udp
    {
        p.extract(headers.udp);
        transition select(headers.udp.dst_port)
        {
            VXLAN_PORT : parse_vxlan;
            default: accept;
        }
    }

    // Built in states
    state parse_roce
    {
        p.extract(headers.bth);
        transition accept;
    }
    
    state parse_vxlan { transition accept; }
    state parse_ptp { transition accept; }
    state parse_mpls { transition accept; }
    state parse_grh { transition accept; }
    state parse_fcoe { transition accept; }
    state parse_control { transition accept; }
    state parse_raw { transition accept; }
}

control Deparser(packet_out b, in Headers_t headers) {
    apply {
        b.emit(headers.ethernet);
        b.emit(headers.vlan);
        b.emit(headers.ip.v4);
        b.emit(headers.udp);
        b.emit(headers.tcp);
    }
}

control ingress(inout Headers_t headers, inout user_metadata_t user_meta, inout standard_metadata_t standard_metadata){

    //action set_bridge(sai_object_id_t tunnel_id, sai_ip4_t underlay_dip, sai_object_id_t bridge_id){
    //    set_bridge_id(bridge_id);
    //}

    //action to_router(sai_object_id_t router_id) {
    //    go_to_router(router_id);
    //}

    //action to_port(sai_object_id_t port_id) { 
    //    go_to_port(port_id);
    //}

    //table table_ex1{
    //    key = { 
    //        user_meta.ingress_port :exact;
    //    }
    //    actions = {set_bridge;}
    //    size = PORTNUM;
    //}   

    //table table_ex2{
    //    key = { 
    //        headers.ip.v4.dst_addr :exact;
    //    }
    //    actions = {to_router;to_port;}
    //    size=VHOST_TABLE_SIZE;
    //}   


    // pipe
    //apply{
    //    table_ex1.apply();
    //    table_ex2.apply();
    //}
    action _drop() {
        drop_stats.count(1);
        mark_to_drop(standard_metadata);
    }

    action l2_forword(bit<9> port) {
        standard_metadata.egress_port = port;
    }
    table ether_exact {
        actions = {
            l2_forword;
            _drop;
        }
        key = {
            headers.ethernet.dstAddr: exact;
        }
        default_action = _drop();
        @name(".ether_exact_stats")counters = ether_exact_stats;
    }
    apply {
        if (headers.ethernet.isValid()) {
            ether_exact.apply();
        }
}

control egress(inout Headers_t headers, inout user_metadata_t user_meta, inout standard_metadata_t standard_metadata) {
    apply {
    }
}

control verifyChecksum(inout Headers_t headers, inout user_metadata_t user_meta) {
    apply {
    }
}

control computeChecksum(inout Headers_t headers, inout user_metadata_t user_meta) {
    apply {
    }
}

V1Switch(Parser(), verifyChecksum(), ingress(), egress(), computeChecksum(), Deparser()) main;